#include "functionalnetwork.h"
#include "omp.h"

FunctionalNetwork::FunctionalNetwork()
{
    aTemplate = NULL;
	indivMatrix = NULL;
	avgMatrix = NULL;
	meanVal = 0;
	stdVal = 1.0;
	links = NULL;
	renderModeUndoStack.push(LinkRenderer);
}

FunctionalNetwork::FunctionalNetwork(AnatomicalTemplate *templat)
{
    aTemplate = templat;
	indivMatrix = NULL;
	avgMatrix = NULL;
	meanVal = 0;
	stdVal = 1.0;
	links = NULL;
	renderModeUndoStack.push(LinkRenderer);
    init();
}

FunctionalNetwork::~FunctionalNetwork()
{

}


void FunctionalNetwork::init()
{
    if(!aTemplate || !indivMatrix)
        return;

	if(indivMatrix->width()!=indivMatrix->height())
		throw "Matrix must be square";

	int progress = 0;
	//#pragma omp parallel for num_threads(8)
	for(int i = 0;i < indivMatrix->height(); i++)
	{
		for(int j = i+1;j < indivMatrix->width();j++)
		{
			AnatomicalRegion *regA = aTemplate->getRegion(i);
			AnatomicalRegion *regB = aTemplate->getRegion(j);
			double corr = indivMatrix->at(i,j);
			AnatomicalLink *alink = new AnatomicalLink(regA,regB,corr);
			LinkList.push_back(alink);
		}
		progress++;
		emit taskUpdate(progress,indivMatrix->height());
	}
}

void FunctionalNetwork::attachToRenderer(vtkRenderer *render)
{
    for(int i = 0;i < LinkList.size();i++)
        LinkList.at(i)->attachToRenderer(render);
	
	for(int i = 0;i<aTemplate->getNumberOfROIs();i++)
		render->AddActor(aTemplate->getRegion(i)->getNodeActor());

	render->AddActor(aTemplate->getOutlineActor());
}

void FunctionalNetwork::detachFromRenderer(vtkRenderer *render)
{
    for(int i = 0;i < LinkList.size();i++)
        LinkList.at(i)->detachFromRenderer(render);
}

void FunctionalNetwork::setTemplate(AnatomicalTemplate *templat)
{
    aTemplate = templat;
}

void FunctionalNetwork::setCurrentAverageMatrix(CorrelationMatrix *matr)
{
    avgMatrix = matr;
}

void FunctionalNetwork::setCurrentIndividualMatrix(CorrelationMatrix *matr)
{
    indivMatrix = matr;
	recalcMean();
	recalcStd();

	recreateOrderedList();

	emit matrixChanged();
}

void FunctionalNetwork::recreateOrderedList()
{
	if(links)
	{
		links->clear();
		delete links;
	}
	
	links = new QVector<AnatomicalLink*>(LinkList); //copy
	qSort(links->begin(),links->end(),&deviationLessThan);
}

CorrelationMatrix *FunctionalNetwork::getCurrentIndividualMatrix()
{
    return indivMatrix;
}

CorrelationMatrix *FunctionalNetwork::getCurrentAverageMatrix()
{
    return avgMatrix;
}

QVector<AnatomicalLink*>& FunctionalNetwork::getLinksSortedByDeviation()
{
	if(!links)
		recreateOrderedList();

	return const_cast<QVector<AnatomicalLink*>&>(*links);
}

bool deviationLessThan(AnatomicalLink *link1, AnatomicalLink *link2)
{
	double dev1 = (link1->correlation() - (1.0/(link1->distance()*link1->distance())));
	double dev2 = (link2->correlation() - (1.0/(link2->distance()*link2->distance())));
	return abs(dev1)<abs(dev2);
}

AnatomicalTemplate *FunctionalNetwork::getTemplate()
{
    return aTemplate;
}

AnatomicalLink* FunctionalNetwork::getLink(int row, int col) const
{
    int index = getCollectionIndex(row,col);
    if(index != -1)
		return LinkList.at(index);
	return NULL;
}

AnatomicalLink* FunctionalNetwork::getLink(AnatomicalRegion* regA, AnatomicalRegion *regB) const
{
	int idxA = getRegions().indexOf(regA);
	int idxB = getRegions().indexOf(regB);
    
	int index = getCollectionIndex(idxA,idxB);
    if(index != -1)
		return LinkList.at(index);
	return NULL;
}

AnatomicalLink* FunctionalNetwork::getLinkByRegionIds(int id1, int id2) const
{
	for(int i = 0; i < LinkList.size(); i++)
	{
		AnatomicalRegion *regA = LinkList.at(i)->regionA();
		AnatomicalRegion *regB = LinkList.at(i)->regionB();
		if((regA->info().RegionID==id1 &&
			regB->info().RegionID==id2) || (regB->info().RegionID==id1 &&
											regA->info().RegionID==id2))
			return LinkList.at(i);
	}
	return NULL;
}

AnatomicalRegion* FunctionalNetwork::getRegionById(int id) const
{
	return aTemplate->getRegionById(id);
}

int FunctionalNetwork::getCollectionIndex(int row, int col)
{
	if(row>col) //swap
	{
		int tmp = col;
		col = row;
		row = tmp;
	}
    int curIndex = 0;
    for(int i = 0; i < row; i++)
    {
            int itemsThisRow = 89 - i;
            curIndex+=itemsThisRow;
    }
    curIndex+=(col-row-1);
    return curIndex;
}

QVector<AnatomicalLink*>& FunctionalNetwork::getLinks() const
{
	return const_cast<QVector<AnatomicalLink*>&>(LinkList);
}

QVector<AnatomicalRegion*>& FunctionalNetwork::getRegions() const
{
	return aTemplate->getRegions();
}

void FunctionalNetwork::setSelection(const QVector<AnatomicalRegion*>& select)
{
	QVector<AnatomicalLink*> newSelection;
	
	//#pragma omp parallel for num_threads(4)
	for(int i = 0; i < LinkList.size();i++)
	{
		AnatomicalRegion* regA = LinkList.at(i)->regionA();
		AnatomicalRegion* regB = LinkList.at(i)->regionB();
		if(select.size()==1)
		{
			if(select.contains(regA) || select.contains(regB))
				newSelection.push_back(LinkList.at(i));
		}
		else
			if(select.contains(regA) && select.contains(regB))
				newSelection.push_back(LinkList.at(i));
	}
	setSelection(newSelection);
}

void FunctionalNetwork::setSelection(const QVector<AnatomicalLink*>& select)
{
	selectionStack.push(select);
	renderModeUndoStack.push(pmode);
	selectedRegionUndoStack.push(selRegion);
	emit selectionChanged();
	updateView();
}

QVector<AnatomicalLink*>& FunctionalNetwork::getSelection() const
{
	//return const_cast<QVector<AnatomicalLink*>&>(selectedLinksStack.at(0));
	if(selectionStack.size()>0)
		return const_cast<QVector<AnatomicalLink*>&>(selectionStack.top());
	else
	{
		QVector<AnatomicalLink*> *links = new QVector<AnatomicalLink*>(); //empty selection
		return const_cast<QVector<AnatomicalLink*>&>(*links);
	}
}

void FunctionalNetwork::undoSelection()
{
	if(selectionStack.size()>0)
	{
		selectionRedoStack.push(selectionStack.pop());
		renderModeRedoStack.push(renderModeUndoStack.pop());
		selectedRegionRedoStack.push(selectedRegionUndoStack.pop());
		emit selectionChanged();
	}
}

void FunctionalNetwork::redoSelection()
{
	if(selectionRedoStack.size()>0)
	{
		selectionStack.push(selectionRedoStack.pop());
		renderModeUndoStack.push(renderModeRedoStack.pop());
		selectedRegionUndoStack.push(selectedRegionRedoStack.pop());
		emit selectionChanged();
	}
}

void FunctionalNetwork::updateView()
{	
	emit viewUpdateRequired();
}

void FunctionalNetwork::update()
{
	emit viewUpdateRequired();
}

double FunctionalNetwork::minimumDistance() const
{
	double min = 99999999;
	for(int i =0; i < LinkList.size(); i++)
		if(LinkList.at(i)->distance()<min)
			min = LinkList.at(i)->distance();
	return min;
}

double FunctionalNetwork::maximumDistance() const
{
	double max = 0;
	for(int i =0; i < LinkList.size(); i++)
		if(LinkList.at(i)->distance()>max)
			max = LinkList.at(i)->distance();
	return max;
			
}

void FunctionalNetwork::recalcMean()
{
	double devMean = 0;
	for(int i = 0;i<LinkList.size();i++)
	{
		AnatomicalLink* link = LinkList.at(i);
		double dev = ((1.0/link->distance()) - abs(link->correlation()));
		devMean+=dev;
	}
	devMean/=LinkList.size();
	meanVal = devMean;
}

void FunctionalNetwork::recalcStd()
{
	double std = 0;
	for(int i = 0;i<LinkList.size();i++)
	{
		AnatomicalLink* link = LinkList.at(i);
		double dev = ((1.0/link->distance()) - abs(link->correlation()));
		double ddev = (dev-meanVal)*(dev-meanVal);
		std+=ddev;
	}
	std/=LinkList.size();
	std = sqrt(std);
	stdVal = std;
}

void FunctionalNetwork::addFilter(LinkFilter *filt)
{
	Filters.push_back(filt);
	connect(filt,SIGNAL(filterChanged(LinkFilter*)),this,SLOT(on_filterChanged(LinkFilter*)));
}

void FunctionalNetwork::on_filterChanged(LinkFilter* filt)
{
	for(int i = 0;i < LinkList.size();i++)
	{
		bool enabled = true;
		
		for(int j = 0;j < Filters.size();j++)
			enabled&=Filters.at(j)->accepts(LinkList.at(i));
		
		LinkList.at(i)->setEnabled(enabled);
	}

	emit matrixChanged(); //re-use this signal instead of creating a new one (causes views to update)
}